package org.holoeverywhere.slider; import android.content.Context; import android.content.res.TypedArray; import android.os.Bundle; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager.OnBackStackChangedListener; import android.support.v4.app.FragmentTransaction; import android.support.v7.app.ActionBar; import android.view.View; import android.widget.ImageView; import org.holoeverywhere.HoloEverywhere; import org.holoeverywhere.LayoutInflater; import org.holoeverywhere.ThemeManager; import org.holoeverywhere.addon.AddonSlider.AddonSliderA; import org.holoeverywhere.addon.IAddonThemes; import org.holoeverywhere.app.Activity; import org.holoeverywhere.app.ContextThemeWrapperPlus; import org.holoeverywhere.app.Fragment; import org.holoeverywhere.drawable.ColorDrawable; import org.holoeverywhere.widget.ExpandableListView; import org.holoeverywhere.widget.ListView; import org.holoeverywhere.widget.TextView; import org.holoeverywhere.widget.ViewStubHolo; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; public class SliderMenu implements OnBackStackChangedListener, IMenuAdder<SliderItem> { public static final int[] BLUE = new int[]{ R.color.holo_blue_dark, R.color.holo_blue_light }; public static final int[] GREEN = new int[]{ R.color.holo_green_dark, R.color.holo_green_light }; public static final int[] ORANGE = new int[]{ R.color.holo_orange_dark, R.color.holo_orange_light }; public static final int[] PURPLE = new int[]{ R.color.holo_purple_dark, R.color.holo_purple_light }; public static final int[] RED = new int[]{ R.color.holo_red_dark, R.color.holo_red_light }; public static final int THEME_FLAG; private static final IAddonThemes sThemes; /** * Change color of small line from left of item */ public static final int FLAG_SELECTION_HANDLER = 1 << 1; /** * Change background color of item */ public static final int FLAG_SELECTION_BACKGROUND = 1 << 2; /** * Allow to keep visible subitems only on group in the same time */ public static final int FLAG_EXPAND_ONE_GROUP = 1 << 3; static final int GROUP_POSITION_SHIFT = 16; static final int GROUP_POSITION_MASK = 0xFFFFFFFF << GROUP_POSITION_SHIFT; // 0xFFFF0000 static final int CHILD_POSITION_MASK = 0xFFFFFFFF >>> (32 - GROUP_POSITION_SHIFT); // 0x0000FFFF static final int GROUP_INVALID = 0xFFFF << GROUP_POSITION_SHIFT; // 0xFFFF0000 static final int CHILD_INVALID = 0xFFFF; // 0x0000FFFF static final int ITEM_INVALID = GROUP_INVALID | CHILD_INVALID; // 0xFFFFFFFF static { sThemes = new IAddonThemes(); THEME_FLAG = sThemes.getThemeFlag(); mapGlobal(R.style.Holo_Theme_Slider, R.style.Holo_Theme_Slider_Light, R.style.Holo_Theme_Slider_Light_DarkActionBar); } private static final String KEY_CURRENT_PAGE = ":slider:currentPage"; private static final int MENU_VIEW_ID = R.id.slider_menu; private final AddonSliderA mAddon; private final FragmentManager mFragmentManager; private ActionBar mActionBar; private IAdapter mAdapter; private int mFuturePosition = 0; private boolean mHandleHomeKey; private boolean mIgnoreBackStack = false; private int mInitialPage = 0; private boolean mInverseTextColorWhenSelected = false; private NavigateUpBehavior mNavigateUpBehavior = NavigateUpBehavior.ShowMenu; private ViewStubHolo mListViewStub; private Context mListViewStubContext; private int mAppearanceFlags; private boolean mExpandableMenu; private IAddonThemes mThemes; private OnPageChangeListener mOnPageChangeListener; private boolean mPageWasChanged = false; final List<SliderItem> mItems; int mCurrentPage = 0; public SliderMenu(AddonSliderA addon) { mAddon = addon; mActionBar = addon.get().getSupportActionBar(); mFragmentManager = mAddon.get().getSupportFragmentManager(); mItems = new ArrayList<SliderItem>(); mThemes = new IAddonThemes(sThemes); mAppearanceFlags = FLAG_SELECTION_BACKGROUND | FLAG_SELECTION_HANDLER | FLAG_EXPAND_ONE_GROUP; } public void addAppearanceFlags(int appearanceFlags) { mAppearanceFlags |= appearanceFlags; } public void removeAppearanceFlags(int appearanceFlags) { mAppearanceFlags &= ~appearanceFlags; } public void setAppearanceFlags(int appearanceFlags) { mAppearanceFlags = appearanceFlags; } public int getAppearanceFlags() { return mAppearanceFlags; } public int getThemeForType(int type) { return mThemes.getTheme(type); } /** * Remap all SliderMenu themes */ public static void mapGlobal(int theme) { mapGlobal(theme, theme, theme); } /** * Remap SliderMenu themes, splited by dark and light color scheme. For * mixed color scheme will be using dark theme */ public static void mapGlobal(int darkTheme, int lightTheme) { mapGlobal(darkTheme, lightTheme, darkTheme); } /** * Remap SliderMenu themes, splited by color scheme */ public static void mapGlobal(int darkTheme, int lightTheme, int mixedTheme) { sThemes.map(darkTheme, lightTheme, mixedTheme); } private static void setTextAppearance(TextView textView, int resid) { if (resid != 0 && textView != null) { textView.setTextAppearance(textView.getContext(), resid); } } @Override public SliderItem add(CharSequence label) { return add(label, null, null, null); } @Override public SliderItem add(CharSequence label, int[] colors) { return add(label, null, null, colors); } @Override public SliderItem add(CharSequence label, Class<? extends Fragment> fragmentClass) { return add(label, fragmentClass, null, null); } @Override public SliderItem add(CharSequence label, Class<? extends Fragment> fragmentClass, Bundle fragmentArguments) { return add(label, fragmentClass, fragmentArguments, null); } @Override public SliderItem add(CharSequence label, Class<? extends Fragment> fragmentClass, Bundle fragmentArguments, int[] colors) { return add(new SliderItem() .setLabel(label) .setFragmentClass(fragmentClass) .setFragmentArguments(fragmentArguments) ).fillColors(colors); } @Override public SliderItem add(int label) { return add(label, null, null, null); } @Override public SliderItem add(int label, int[] colors) { return add(label, null, null, colors); } @Override public SliderItem add(CharSequence label, Class<? extends Fragment> fragmentClass, int[] colors) { return add(label, fragmentClass, null, colors); } @Override public SliderItem add(int label, Class<? extends Fragment> fragmentClass) { return add(label, fragmentClass, null, null); } @Override public SliderItem add(int label, Class<? extends Fragment> fragmentClass, Bundle fragmentArguments) { return add(label, fragmentClass, fragmentArguments, null); } @Override public SliderItem add(int label, Class<? extends Fragment> fragmentClass, Bundle fragmentArguments, int[] colors) { return add(mAddon.get().getText(label), fragmentClass, fragmentArguments, colors); } @Override public SliderItem add(int label, Class<? extends Fragment> fragmentClass, int[] colors) { return add(label, fragmentClass, null, colors); } @Override public SliderItem add(SliderItem item) { if (item.mSliderMenu != null) { throw new IllegalArgumentException("Item already has a parent: " + item + " (" + item.mSliderMenu + ")"); } item.mSliderMenu = this; mItems.add(item); notifyChanged(); return item; } @Override public SliderItem add(SliderItem item, int position) { if (item.mSliderMenu != null) { throw new IllegalArgumentException("Item already has a parent: " + item + " (" + item.mSliderMenu + ")"); } item.mSliderMenu = this; mItems.add(position, item); notifyChanged(); return item; } public void bind(Fragment listFragment) { bind(listFragment, null); } public void bind(Fragment listFragment, Context context) { bind(listFragment.getView().findViewById(MENU_VIEW_ID), context); } private void bind(View view, Context context) { if (view instanceof ViewStubHolo) { mListViewStub = (ViewStubHolo) view; mListViewStubContext = context; return; } while (true) { if (view instanceof ExpandableListView) { bind((ExpandableListView) view, context); return; } else if (view instanceof ListView) { bind((ListView) view, context); return; } else if (view != null) { view = view.findViewById(MENU_VIEW_ID); } else { break; } } throw new RuntimeException("Couldn't bind menu on view"); } private boolean hasSubItems() { for (SliderItem item : mItems) { if (item.hasSubItems()) { return true; } } return false; } public void bind(ListView listView, Context context) { if (mAdapter != null) { throw new IllegalStateException("No more than one view allowed"); } mExpandableMenu = false; SliderMenuAdapter adapter = new SliderMenuAdapter(obtainMenuContext(context == null ? listView.getContext() : context), this); mAdapter = adapter; adapter.bind(listView); } public void bind(ExpandableListView listView, Context context) { if (mAdapter != null) { throw new IllegalStateException("No more than one view allowed"); } mExpandableMenu = true; SliderMenuExpandableAdapter adapter = new SliderMenuExpandableAdapter(obtainMenuContext(context == null ? listView.getContext() : context), this); mAdapter = adapter; adapter.bind(listView); } public void bindOnLeftPanel() { bindOnLeftPanel(null); } public void bindOnLeftPanel(Context context) { bind(mAddon.getLeftView(), context); } public void bindOnRightPanel() { bindOnRightPanel(null); } public void bindOnRightPanel(Context context) { bind(mAddon.getRightView(), context); } public int encodePage(int groupPosition, int childPosition) { if ((childPosition & CHILD_POSITION_MASK) != childPosition) { throw new RuntimeException("Ooops. Items too many you have, young padawan"); } return (groupPosition << GROUP_POSITION_SHIFT) | childPosition; } public void decodePage(int position, int[] array) { array[0] = (position & GROUP_POSITION_MASK) >>> GROUP_POSITION_SHIFT; array[1] = position & CHILD_POSITION_MASK; } @SuppressWarnings("deprecation") View bindView(BaseSliderItem<?> item, View view, boolean selected, int defaultTextAppearance, int defaultTextAppearanceInverse) { TextView labelView = (TextView) view.findViewById(android.R.id.text1); if (labelView != null) { labelView.setText(item.mLabel); } final int textAppearance = item.mTextAppereance != 0 ? item.mTextAppereance : defaultTextAppearance; final int textAppearanceInverse = item.mTextAppereanceInverse != 0 ? item.mTextAppereanceInverse : defaultTextAppearanceInverse; setTextAppearance(labelView, mInverseTextColorWhenSelected && selected ? textAppearanceInverse : textAppearance); ImageView iconView = (ImageView) view.findViewById(android.R.id.icon1); iconView.setImageDrawable(item.mIcon); iconView.setVisibility(item.mIcon == null ? View.GONE : View.VISIBLE); return bindAppearanceView(view, selected, item); } private View bindAppearanceView(View view, boolean selected, BaseSliderItem<?> item) { View selectionHandlerView = view.findViewById(R.id.selectionHandler); View backgroundGroupIndicator = view.findViewById(R.id.groupIndicator); if (flag(FLAG_SELECTION_HANDLER) && selectionHandlerView != null) { selectionHandlerView.setBackgroundDrawable(selected ? new ColorDrawable(item.mSelectionHandlerColor) : null); } if (flag(FLAG_SELECTION_BACKGROUND)) { view.setBackgroundDrawable(selected ? new ColorDrawable(item.mBackgroundColor) : null); } if (flag(FLAG_SELECTION_HANDLER) && item instanceof SliderSubItem && backgroundGroupIndicator != null) { backgroundGroupIndicator.setBackgroundDrawable(new ColorDrawable(((SliderSubItem) item).mParentItem.mSelectionHandlerColor)); } return view; } protected void changePage(int position) { changePage(position, getItemFromPosition(position)); } private void changePage(int position, BaseSliderItem<?> item) { mIgnoreBackStack = true; if (mCurrentPage >= 0) { final BaseSliderItem<?> lastItem = getItemFromPosition(mCurrentPage); final WeakReference<Fragment> ref = lastItem.mLastFragment; final Fragment fragment = ref == null ? null : ref.get(); if (fragment != null && fragment.isAdded()) { if (!fragment.isDetached()) { mFragmentManager.beginTransaction().detach(fragment).commit(); mFragmentManager.executePendingTransactions(); } if (lastItem.mSaveState) { lastItem.mSavedState = mFragmentManager.saveFragmentInstanceState(fragment); } } } mCurrentPage = position; mAdapter.notifyDataSetInvalidated(); clearBackStack(); if (item.mFragmentClass != null) { final Fragment fragment = Fragment.instantiate(item.mFragmentClass); if (item.mSavedState != null) { fragment.setInitialSavedState(item.mSavedState); } if (item.mFragmentArguments != null) { item.mFragmentArguments.setClassLoader(HoloEverywhere.class.getClassLoader()); fragment.setArguments(item.mFragmentArguments); } item.mLastFragment = new WeakReference<Fragment>(fragment); final String tag; if (item.mTag != null) { tag = item.mTag; } else { tag = "slider-fragment-" + fragment.hashCode(); } clearBackStack(); replaceFragment(mFragmentManager, fragment, tag, false); } mIgnoreBackStack = false; } private void clearBackStack() { while (mFragmentManager.popBackStackImmediate()) { ; } } public int getInitialPage() { return mInitialPage; } public void setInitialPage(int initialPage) { mInitialPage = initialPage; } public int getCurrentPage() { return mCurrentPage; } public void setCurrentPage(int position) { mPageWasChanged = true; setCurrentPage(position, true, true); } public int indexOfItem(SliderItem item) { return mItems.indexOf(item); } public void invalidate() { if (mAdapter != null) { mAdapter.notifyDataSetInvalidated(); } } public boolean isHandleHomeKey() { return mHandleHomeKey; } public void setHandleHomeKey(boolean handleHomeKey) { mHandleHomeKey = handleHomeKey; } public boolean isInverseTextColorWhenSelected() { return mInverseTextColorWhenSelected; } public void setInverseTextColorWhenSelected(boolean inverseTextColorWhenSelected) { mInverseTextColorWhenSelected = inverseTextColorWhenSelected; } public void makeDefaultMenu() { makeDefaultMenu(mAddon.obtainMenuContext(this)); } public void makeDefaultMenu(Context context) { setInverseTextColorWhenSelected(ThemeManager.getThemeType(mAddon.get()) != ThemeManager.LIGHT); Fragment menuFragment = (Fragment) mFragmentManager.findFragmentById(R.id.leftView); if (menuFragment == null) { menuFragment = (Fragment) mFragmentManager.findFragmentById(R.id.rightView); } if (menuFragment == null) { throw new IllegalStateException("Couldn't find Fragment for menu"); } bind(menuFragment, context); } public NavigateUpBehavior getNavigateUpBehavior() { return mNavigateUpBehavior; } public void setNavigateUpBehavior(NavigateUpBehavior behavior) { mNavigateUpBehavior = behavior; } void notifyChanged() { if (mAdapter != null) { mAdapter.notifyDataSetChanged(); } } @Override public void onBackStackChanged() { if (mHandleHomeKey) { mActionBar.setDisplayHomeAsUpEnabled(mAddon.isAddonEnabled() || mFragmentManager.getBackStackEntryCount() > 0); } if (mIgnoreBackStack) { return; } BaseSliderItem<?> item = getItemFromPosition(mCurrentPage); WeakReference<Fragment> ref = item.mLastFragment; Fragment fragment = ref == null ? null : ref.get(); if (fragment == null || !item.mSaveState) { return; } if (fragment.isAdded() && fragment.isDetached() && !fragment.isVisible()) { item.mSavedState = mFragmentManager.saveFragmentInstanceState(fragment); } } public boolean onNavigateUp() { if (mHandleHomeKey) { if (mNavigateUpBehavior == NavigateUpBehavior.ShowMenu) { mAddon.toggle(); } else if (mNavigateUpBehavior == NavigateUpBehavior.PopUpFragment) { if (mAddon.isAddonEnabled() && mFragmentManager.getBackStackEntryCount() == 0) { mAddon.toggle(); } else { mAddon.get().onBackPressed(); } } return true; } return false; } public void onPostCreate(Bundle savedInstanceState) { if (mListViewStub != null) { if (mListViewStub.getLayoutResource() == 0) { mListViewStub.setLayoutResource(hasSubItems() ? R.layout.slider_default_expandable_list_layout : R.layout.slider_default_list_layout); } if (mListViewStubContext != null) { mListViewStub.setLayoutInflater(LayoutInflater.from(mListViewStubContext)); } bind(mListViewStub.inflate(), mListViewStubContext); mListViewStub = null; mListViewStubContext = null; } if (savedInstanceState != null) { mCurrentPage = savedInstanceState.getInt(KEY_CURRENT_PAGE, 0); } mIgnoreBackStack = true; mAddon.get().getSupportFragmentManager().addOnBackStackChangedListener(this); onBackStackChanged(); mIgnoreBackStack = false; if (!mPageWasChanged && mItems.size() > 0) { setCurrentPage(mFuturePosition, true, true); } } public void onResume() { if (mCurrentPage < 0 && mItems.size() > 0) { setCurrentPage(mInitialPage, true, true); } } public void onSaveInstanceState(Bundle outState) { outState.putInt(KEY_CURRENT_PAGE, mCurrentPage); } public boolean flag(int flag) { return (mAppearanceFlags & flag) == flag; } public void remove(int position) { mItems.remove(position).mSliderMenu = null; notifyChanged(); } public void remove(SliderItem item) { if (mItems.remove(item)) { item.mSliderMenu = null; } notifyChanged(); } public void replaceFragment(Fragment newFragment) { replaceFragment(newFragment, null, true); } public void replaceFragment(Fragment newFragment, String tag, boolean addToBackStack) { if (!addToBackStack) { clearBackStack(); } replaceFragment(mFragmentManager, newFragment, tag, addToBackStack); } private void replaceFragment(FragmentManager fm, Fragment fragment, String tag, boolean addToBackStack) { FragmentTransaction ft = fm.beginTransaction(); ft.replace(R.id.contentView, fragment, tag); ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); if (addToBackStack) { ft.addToBackStack(tag); ft.commitAllowingStateLoss(); } else { ft.commit(); } } void setCurrentPage(int position, boolean force, boolean openContentView) { if (mAdapter == null) { mFuturePosition = position; return; } if (force || mCurrentPage != position || mAddon.get().getSupportFragmentManager().getBackStackEntryCount() > 0) { changePage(position, getItemFromPosition(position)); if (mOnPageChangeListener != null) { mOnPageChangeListener.onPageChange(mCurrentPage, position); } mCurrentPage = position; } if (mAddon.isAddonEnabled() && openContentView) { mAddon.openContentViewDelayed(40); } } private BaseSliderItem<?> getItemFromPosition(int position) { if (mExpandableMenu) { final int group = (position & GROUP_POSITION_MASK) >>> GROUP_POSITION_SHIFT; final int child = (position & CHILD_POSITION_MASK); if (group == GROUP_INVALID) { return null; } SliderItem item = mItems.get(group); if (child == CHILD_INVALID || item.mSubItems == null || item.mSubItems.size() <= child) { return item; } return item.mSubItems.get(child); } else { return mItems.get(position); } } public Activity getActivity() { return mAddon.get(); } public void map(int theme) { map(theme, theme, theme); } public void map(int darkTheme, int lightTheme) { map(darkTheme, lightTheme, darkTheme); } public void map(int darkTheme, int lightTheme, int mixedTheme) { mThemes.map(darkTheme, lightTheme, mixedTheme); } void internalSetOnPageChangeListener(OnPageChangeListener listener) { mOnPageChangeListener = listener; } public Context obtainMenuContext(Context context) { context = sThemes.context(context); TypedArray a = context.obtainStyledAttributes(new int[]{R.attr.sliderMenuTheme}); final int themeRes = a.getResourceId(0, 0); a.recycle(); return themeRes == 0 ? context : new ContextThemeWrapperPlus(context, themeRes); } interface OnPageChangeListener { void onPageChange(int lastPage, int currentPage); } public static enum NavigateUpBehavior { PopUpFragment, ShowMenu } }